package com.ztesoft.zcm.cmdb.util.remote;

import com.ztesoft.zcm.cmdb.config.RemoteProperties;
import com.ztesoft.zcm.cmdb.domain.gen.Server;
import com.ztesoft.zcm.cmdb.domain.gen.ServerUser;
import com.ztesoft.zcm.cmdb.service.AbstractServerService;
import com.ztesoft.zcm.cmdb.service.AbstractServerUserService;
import com.ztesoft.zcm.cmdb.util.CmdbExceptionErrorCode;
import com.ztesoft.zsmart.core.exception.BaseAppException;
import com.ztesoft.zsmart.core.log.ZSmartLogger;
import com.ztesoft.zsmart.zcm.core.exception.ExceptionPublisher;
import com.ztesoft.zsmart.zcm.core.utils.SystemUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 远程连接工厂
 * TODO:
 * 1. 密码修改场景处理
 * 2. 连接异常断开处理
 * 3. 登录用户被删除场景处理
 *
 * @author li.peilong
 * @date 2019/05/22
 **/
@Component
public class RemoteClientFactory {
    private static final ZSmartLogger LOGGER = ZSmartLogger.getLogger(RemoteClientFactory.class);
    private final Map<String, RemoteClientPool> pools = new ConcurrentHashMap<>();
    @Autowired
    private RemoteProperties remoteProperties;
    @Autowired
    private AbstractServerService serverService;
    @Autowired
    private AbstractServerUserService serverUserService;

    /**
     * 用于清理空闲远程客户端
     */
    @PostConstruct
    public void clean() {
        // 添加钩子，用于应用停止时主动关闭连接
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            pools.values().forEach(pool -> pool.close());
            pools.clear();
        }));

        // 定时清理过期远程客户端
        Thread t = new Thread(() -> {
            while (true) {
                SystemUtil.sleepIgnoreError(remoteProperties.getIdleCheckPeriod() * 1000);
                try {
                    pools.values().forEach(pool -> pool.checkIdle());
                }
                catch (Exception e) {
                    LOGGER.warn("Failed to clean idle remote client", e);
                }

            }
        }, "remote-client-clean-thread");
        t.setDaemon(true);
        t.start();
    }

    /**
     * 获取一个远程客户端
     * 注意：客户端使用完后，需在finally中关闭（RemoteClient.close())
     * @param host
     * @param timeout 获取客户端超时时间
     * @return
     */
    public RemoteClient getRemoteClient(String host, int timeout) throws BaseAppException {
        if (!pools.containsKey(host)) {
            synchronized (RemoteClientFactory.class) {
                HostAccessInfo hostAccessInfo = this.getHostAccessInfo(host);
                if (!pools.containsKey(host)) {
                    RemoteClientPool pool = RemoteClientPool.builder()
                            .hostAccessInfo(hostAccessInfo)
                            .timeout(timeout)
                            .minPoolSize(remoteProperties.getMinPoolSize())
                            .maxPoolSize(remoteProperties.getMaxPoolSize())
                            .maxIdleTime(remoteProperties.getMaxIdleTime() * 60 * 1000)
                            .build();
                    pools.put(host, pool);
                }
            }
        }
        return pools.get(host).getRemoteClient();
    }

    /**
     * 获取远程客户端
     * 默认超时时间5s
     *
     * @param host
     * @return
     */
    public RemoteClient getRemoteClient(String host) throws BaseAppException {
        return this.getRemoteClient(host, 5);
    }

    /**
     * 获取主机访问信息
     *
     * @param host
     * @return
     */
    private HostAccessInfo getHostAccessInfo(String host) throws BaseAppException {

        List<Server> servers = serverService.getServer(null, host);
        if (servers == null || servers.isEmpty()) {
            ExceptionPublisher.publish(CmdbExceptionErrorCode.REMOTE_UNKNOWN_HOST, host);
        }

        Server server = servers.get(0);
        List<ServerUser> users = serverUserService.getServerUserByServerId(server.getServerId(), false);
        if (users == null || users.isEmpty()) {
            ExceptionPublisher.publish(CmdbExceptionErrorCode.REMOTE_GET_ACCESS_INFO_FAILED, host);
        }
        ServerUser defaultUser = null;
        for (ServerUser user:users) {
            if ("Y".equals(user.getIsDefault())) {
                defaultUser = user;
                break;
            }
        }
        if (defaultUser == null) {
            defaultUser = users.get(0);
        }

        return HostAccessInfo.builder()
                .host(host)
                .port(server.getSshPort() == null ? 22 : server.getSshPort())
                .user(defaultUser.getUserName())
                .password(defaultUser.getPassWord())
                .build();
    }
}
